package co.baiku.boot.core.orm.service.impl;

import co.baiku.boot.common.exception.ApiException;
import co.baiku.boot.common.message.PageData;
import co.baiku.boot.common.spring.SpringContext;
import co.baiku.boot.common.tools.GenericsTools;
import co.baiku.boot.common.tools.ObjectTools;
import co.baiku.boot.common.tools.StringTools;
import co.baiku.boot.common.type.TimeFormat;
import co.baiku.boot.core.orm.bin.RunSQL;
import co.baiku.boot.core.orm.dao.BaseDAO;
import co.baiku.boot.core.orm.dto.mapper.IMapper;
import co.baiku.boot.core.orm.po.TenantPo;
import co.baiku.boot.core.orm.request.PagingRequest;
import co.baiku.boot.core.orm.request.RequestFilter;
import co.baiku.boot.core.orm.request.SearchGroup;
import co.baiku.boot.core.orm.service.BaseService;
import co.baiku.boot.core.orm.specification.JpaSpecification;
import co.baiku.boot.core.orm.tools.JpaTools;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.*;

public class BaseServiceImpl<T, D, ID extends Serializable> implements BaseService<T, D, ID> {
    protected BaseDAO<T, ID> baseDAO;

    protected T entity;
    private final String DEL = "EQ_del";

    @Resource
    private RunSQL runSQL;

    public BaseServiceImpl(BaseDAO<T, ID> baseDAO) {
        this.entity = instanceAnnotationObject();
        this.baseDAO = baseDAO;
    }

    @SuppressWarnings("unchecked")
    protected T instanceAnnotationObject() {
        try {
            return (T) GenericsTools.getSuperClassGenricType(getClass(), 0)
                    .newInstance();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    protected PageData<T> findPaging(Integer page, Integer rows, Specification<T> specification, Sort sort) {
        Specification<T> requisite = logicDeleteRequisite();
        specification = requisite.and(specification);
        Page<T> result = baseDAO.findAll(specification, PageRequest.of(page - 1, rows, sort));
        return PageData.of(page, rows, (int) result.getTotalElements(), result.getContent());
    }

    @Override
    public PageData<T> findPaging(PagingRequest request) {
        return findPaging(request.getPageNo(), request.getPageSize(), request.getSearch(), request.getSearchGroup(), JpaTools.sort(request.getSortBy()));
    }

    protected PageData<T> findPaging(Integer page, Integer rows, Map<String, Object> searchParams, List<SearchGroup> searchGroups, Sort sort) {
        Specification<T> spec = buildSpecification(searchParams);
        if (ObjectTools.isNotBlank(searchGroups)) {
            spec = spec.and(buildSpecification(searchGroups));
        }
        return findPaging(page, rows, spec, sort);
    }

    @Override
    public PageData<D> findDtoPaging(Integer page, Integer rows, Map<String, Object> searchParams, Map<String, String> sort) {
        return findDtoPaging(page, rows, searchParams, null, sort);
    }

    @Override
    public PageData<D> findDtoPaging(Integer page, Integer rows, Map<String, Object> searchParams, List<SearchGroup> searchGroups, Map<String, String> sort) {
        Specification<T> spec = buildSpecification(searchParams);
        if (ObjectTools.isNotBlank(searchGroups)) {
            spec = spec.and(buildSpecification(searchGroups));
        }
        return findDtoPaging(page, rows, spec, sort);
    }

    private PageData<D> findDtoPaging(Integer page, Integer rows, Specification<T> specification, Map<String, String> sort) {
        Specification<T> requisite = logicDeleteRequisite();
        specification = requisite.and(specification);
        Page<T> daoRes = baseDAO.findAll(specification, PageRequest.of(page - 1, rows, JpaTools.sort(sort)));
        if (ObjectTools.isNotBlank(daoRes.getContent())) {
            return PageData.of(daoRes.getPageable().getPageNumber() + 1, daoRes.getPageable().getPageSize(), (int) daoRes.getTotalElements(), toDto(daoRes.getContent()));
        }
        return PageData.of(daoRes.getPageable().getPageNumber() + 1, daoRes.getPageable().getPageSize(), (int) daoRes.getTotalElements(), Collections.EMPTY_LIST);
    }

    @Override
    public PageData<D> findDtoPaging(PagingRequest request) {
        return findDtoPaging(request.getPageNo(), request.getPageSize(), request.getSearch(), request.getSearchGroup(), request.getSortBy());
    }

    @Override
    public List<T> findAll(Map<String, Object> searchParams) {
        return findAll(searchParams, null);
    }

    @Override
    public List<T> findAll(Map<String, Object> searchParams, Map<String, String> sort) {
        processQueryField(searchParams);
        Specification<T> spec = buildSpecification(searchParams);
        return findAll(spec, sort);
    }

    public List<T> findAll(Specification<T> spec, Map<String, String> sort) {
        Specification<T> requisite = logicDeleteRequisite();
        spec = requisite.and(spec);
        if (sort != null) {
            return baseDAO.findAll(spec, JpaTools.sort(sort));
        } else {
            return baseDAO.findAll(spec);
        }
    }

    @Override
    public List<T> findAll() {
        return findByProperty(DEL, false);
    }

    @Override
    public List<T> findByProperty(String property, Object value, Map<String, String> sort) {
        Map<String, Object> searchParams = new HashMap<>();
        searchParams.put(property, processQueryField(property, value));
        return findAll(searchParams, sort);
    }

    @Override
    public List<T> findByProperty(String property, Object value) {
        return findByProperty(property, processQueryField(property, value), null);
    }

    /**
     * 创建动态查询条件组合.
     */
    protected Specification<T> buildSpecification(Map<String, Object> searchParams) {
        processQueryField(searchParams);
        return new JpaSpecification<>(RequestFilter.parse(searchParams).values());
    }

    /**
     * 创建动态查询条件组合.
     */
    protected Specification<T> buildSpecification(List<SearchGroup> searchGroups) {
        Specification<T> result = null;
        for (SearchGroup searchGroup : searchGroups) {
            Map<String, Object> search = searchGroup.getSearch();
            processQueryField(search);
            Map<String, RequestFilter> parse = RequestFilter.parse(search);

            for (String key : parse.keySet()) {
                RequestFilter requestFilter = parse.get(key);
                JpaSpecification<T> item = new JpaSpecification<>(requestFilter);
                if (result == null) {
                    result = item;
                    continue;
                }
                if (searchGroup.getOperator() == RequestFilter.Operator.OR) {
                    result = result.or(item);
                } else {
                    result = result.and(item);
                }
            }

        }
        return result;
    }

    @Override
    public D toDto(T entity) {
        return (D) mapper().toDto(entity);
    }

    @Override
    public List<D> toDto(List<T> entity) {
        return mapper().toDto(entity);
    }

    @Override
    public PageData<D> toDto(PageData<T> page) {
        return PageData.of(page.getPageNo(), page.getPageSize(), page.getTotalCount(), toDto(page.getData()));
    }

    @Override
    public T toPo(D dto) {
        return (T) mapper().toPo(dto);
    }

    @Override
    public List<T> toPo(List<D> dtos) {
        return mapper().toPo(dtos);
    }

    @Override
    public T getById(ID id) {
        if (id == null) {
            return null;
        }
        T result = baseDAO.getById(id);
        if (result != null && ((TenantPo) result).getDel()) {
            return null;
        }
        return result;
    }

    @Override
    public D getDtoById(ID id) {
        return toDto(getById(id));
    }

    @Override
    public T saveOrUpdate(T entity) {
        if (entity == null) {
            return null;
        }
        if (((TenantPo) entity).getDel() == null) {
            ((TenantPo) entity).setDel(false);
        }
        return baseDAO.saveAndFlush(entity);
    }

    @Override
    public int dynamicUpdate(T entity) {
        return baseDAO.dynamicUpdate(entity);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchInsert(Collection<T> entites) {
        baseDAO.batchSave(entites);
        return entites.size();
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int batchUpdate(Collection<T> entites) {
        baseDAO.batchSave(entites);
        return entites.size();
    }

    @Override
    public void delete(ID id) {
        if (id != null) {
            delete(baseDAO.getById(id));
        }
    }

    @Override
    public void delete(Collection<ID> ids) {
        if (ObjectTools.isNotBlank(ids)) {
            deleteAll(baseDAO.findAllById(ids));
        }
    }

    @Override
    public void delete(T entity) {
        if (entity != null) {
            ((TenantPo) entity).setDel(true);
            baseDAO.save(entity);
        }
    }

    @Override
    public void deleteAll(Collection<T> entity) {
        if (ObjectTools.isNotBlank(entity)) {
            for (T t : entity) {
                delete(t);
            }
        }
    }

    @Override
    public List<T> findByIdIn(Collection<ID> ids) {
        return baseDAO.findByIdIn(ids);
    }

    @Override
    public void deleteByProperty(String property, Object value) {
        deleteAll(findByProperty(property, value));
    }

    @Override
    public void deleteByParams(Map<String, Object> searchParams) {
        deleteAll(findAll(searchParams));
    }

    /**
     * 处理查询参数中有查询字段为Date 或者  DateTime 类型的
     *
     * @param params
     */
    protected void processQueryField(Map<String, Object> params) {
        if (ObjectTools.isNotBlank(params)) {
            for (Map.Entry<String, Object> entry : params.entrySet()) {
                entry.setValue(processQueryField(entry.getKey(), entry.getValue()));
            }
        }
    }


    /**
     * 处理查询参数中有查询字段为Date 或者  DateTime 类型的
     *
     * @param property 查询条件，可识别类似 EQ_status  ||  status 这两种情况
     * @param value    查询值
     */
    protected Object processQueryField(String property, Object value) {
        if (StringTools.isNotBlank(property) && ObjectTools.isNotBlank(value)) {
            String[] arr = property.split(StringTools.UNDERLINE);
            String fieldType = runSQL.getPoFieldTypeName(entity.getClass(), arr.length < 2 ? property : arr[1]);
            if (StringTools.isBlank(fieldType)) {
                return value;
            } else if (!(value instanceof LocalDateTime) && LocalDateTime.class.getSimpleName().equalsIgnoreCase(fieldType)) {
                return TimeFormat.toTime((String) value, TimeFormat.DATETIME);
            } else if (!(value instanceof LocalDate) && LocalDate.class.getSimpleName().equalsIgnoreCase(fieldType)) {
                return TimeFormat.toDate((String) value, TimeFormat.DATE);
            }
        }
        return value;
    }

    @Override
    public IMapper<T, D> mapper() {
        String simpleName = entity.getClass().getSimpleName();
        IMapper<T, D> mapperImpl = SpringContext.getIfPresent(StringTools.firstLetterToLower(simpleName + "MapperImpl"));
        if (mapperImpl == null) {
            throw new ApiException("没有创建" + simpleName + "Mapper");
        }
        return mapperImpl;
    }

    /**
     * 拼装del参数
     *
     * @return 动态查询条件
     */
    public Specification<T> logicDeleteRequisite() {
        Map<String, Object> searchParams = new HashMap<>();
        searchParams.put(DEL, false);
        return buildSpecification(searchParams);
    }
}
